/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - core/memory
// Copyright( c) 2013.  All Rights Reserved
//
// File:		AEMiniAllocator.cpp
// Author:		Gianluca Belardelli
// Date:		18/11/2013
//
/////////////////////////////////////////////////////////////////////////
#include <AECore.h>
#include <core\memory\AEMiniAllocator.h>

struct MemoryHeader
{
	MemoryHeader *lpNext;
};
/*
class CMicroChunkUpdate
{
public:
	virtual void AddMicroChunk( AEUINT8 *lpMemStart, AEUINT8 *lpMemEnd, CMemChunk *lpChunk ) = 0;
	virtual void RemoveMicroChunk( CMemChunk *lpChunk ) = 0;
};
*/
class CMemChunk
{
// Members
private:
	bool			m_bMyHeap;
	AEUINT8			*m_lpData;
	AEUINT8			*m_lpDataEnd;
	AEUINT32		m_uiUsedCount;
	MemoryHeader	*m_lpFreeList;
	AEUINT32		m_uiChunkSize;
	
// Methods
public:
	CMemChunk( void )
	{
		m_bMyHeap = false;
		m_lpData = 0L;
		m_lpDataEnd = 0L;
		m_uiUsedCount = 0;
		m_lpFreeList = 0L;
		m_uiChunkSize = 0;
	}

	AE_FORCEINLINE AEUINT32 GetChunkSize( void ) const { return m_uiChunkSize; }

	AEUINT8 *Init( AEUINT8 *lpChunkBase, AEUINT32 uiChunkSize, AEUINT32 uiMaxChunks )
	{
		m_uiChunkSize = uiChunkSize;
		m_lpData = lpChunkBase;
		m_lpDataEnd = m_lpData + ( uiChunkSize * uiMaxChunks );
		m_lpFreeList = (MemoryHeader *)m_lpData;

		MemoryHeader *lpScan = m_lpFreeList;
		AEUINT8 *lpData = m_lpData;

		lpData+=uiChunkSize;

		for( AEUINT32 i=0; i<(uiMaxChunks-1); i++ )
		{
			MemoryHeader *next = (MemoryHeader *)lpData;
			lpScan->lpNext = next;
			lpData+=uiChunkSize;
			lpScan = next;
		}

		lpScan->lpNext = 0L;
		return m_lpDataEnd;
	}

	AE_FORCEINLINE void *Allocate( AEUINT32 uiChunkSize, AEUINT32 uiMaxChunks )
	{
		AEASSERT( m_lpData != 0L ); // No new allocations allowed
		AEASSERT( m_lpFreeList );

		void *lpRet = 0L;

		/*if ( m_lpData == 0L )
		{
			m_bMyHeap = true;
			m_lpData = (AEUINT8 *)heap->micro_malloc( uiChunkSize * uiMaxChunks );
			Init( m_lpData, uiChunkSize, uiMaxChunks );
			lpUpdate->AddMicroChunk( m_lpData, m_lpDataEnd, this );
		}*/

		//if ( m_lpFreeList )
		//{
			m_uiUsedCount++;
			lpRet = m_lpFreeList;
			m_lpFreeList = m_lpFreeList->lpNext;
		//}

		return lpRet;
	}

	AE_FORCEINLINE void Deallocate( void *lpPtr )
	{
		#ifdef _DEBUG
			AEASSERT( m_uiUsedCount );
			AEUINT8 *lpTmp = (AEUINT8 *)lpPtr;
			AEASSERT( lpTmp >= m_lpData && lpTmp < m_lpDataEnd );
		#endif

		MemoryHeader *lpMH = m_lpFreeList;
		m_lpFreeList = (MemoryHeader *)lpPtr;
		m_lpFreeList->lpNext = lpMH;
		m_uiUsedCount--;

		if ( m_uiUsedCount == 0 && m_bMyHeap  ) // free the heap back to the application if we are done with this.
		{
			//lpUpdate->RemoveMicroChunk( this );
			//lpHeap->micro_free( m_lpData );
			m_bMyHeap = false;
			m_lpData   = 0L;
			m_lpDataEnd = 0L;
			m_lpFreeList = 0L;
		}
	}

	AE_FORCEINLINE bool IsInside( const AEUINT8 *lpPtr ) const
	{
		return lpPtr >= m_lpData && lpPtr < m_lpDataEnd;
	}

};

class CMicroChunk
{
// Members
public:
	AEUINT8		*m_lpMemStart;
	AEUINT8		*m_lpMemEnd;
	CMemChunk	*m_lpChunk;
	AEUINT8		*m_uiPad; // padding to make it 16 byte aligned.

// Methods
public:
	AE_FORCEINLINE void Set( AEUINT8 *lpMemStart, AEUINT8 *lpMemEnd, CMemChunk *lpMC )
	{
		m_lpMemStart = lpMemStart;
		m_lpMemEnd   = lpMemEnd;
		m_lpChunk    = lpMC;
		m_uiPad      = 0;
	}

	AE_FORCEINLINE bool Inside( const AEUINT8 *lpPtr ) const
	{
		return lpPtr >= m_lpMemStart && lpPtr < m_lpMemEnd;
	}
};

#define DEFAULT_CHUNKS 32

class CMemChunkList
{
// Members
public:
	AEUINT32		m_uiChunkSize;
	AEUINT32		m_uiMaxChunks;
	CMemChunkList	*m_lpNext;
	CMemChunk		m_mcChunks[DEFAULT_CHUNKS];
// Methods
public:
	AE_DECLARE_PLACEMENT_ALLOCATOR();

	CMemChunkList( void )
	{
		m_lpNext = 0L;
		m_uiChunkSize = 0;
		m_uiMaxChunks = 0;
	}

	~CMemChunkList( void ) { }

	AE_FORCEINLINE void *Allocate( CMemChunk *&lpRefCurrent )
	{
		void *lpRet = 0L;

		CMemChunkList *lpScan = this;
		while ( lpScan && lpRet == 0L )
		{
			for( AEUINT32 i=0; i<DEFAULT_CHUNKS; i++ )
			{
				lpRet = lpScan->m_mcChunks[i].Allocate( m_uiChunkSize, m_uiMaxChunks );
				if ( lpRet )
				{
					lpRefCurrent = &lpScan->m_mcChunks[i];
					lpScan = 0L;
					break;
				}
			}
			if ( lpScan )
				lpScan = lpScan->m_lpNext;
		}

		if ( !lpRet )
		{
			//CMemChunkList *lpMCL = (CMemChunkList *)m_lpHeap->micro_malloc( sizeof(CMemChunkList) );
			//new ( lpMCL ) CMemChunkList();
			//CMemChunkList *lpNext = m_lpNext;
			//m_lpNext = lpMCL;
			//lpMCL->m_lpNext = lpNext;
			//lpRet = lpMCL->m_mcChunks[0].Allocate( m_uiChunkSize, m_uiMaxChunks, lpUpdate );
			//lpRefCurrent = &lpMCL->m_mcChunks[0];
		}

		return lpRet;
	}

	AEUINT8 *Init( AEUINT8 *lpChunkBase, AEUINT32 uiFixedSize, AEUINT32 uiChunkSize, CMemChunk *&lpRefCurrent )
	{
		m_uiChunkSize = uiChunkSize;
		m_uiMaxChunks = uiFixedSize/m_uiChunkSize;
		lpRefCurrent = &m_mcChunks[0];
		lpChunkBase = m_mcChunks[0].Init( lpChunkBase, uiChunkSize, m_uiMaxChunks );
		return lpChunkBase;
	}
};

class CFixedMemory
{
// Members
public:
	AEUINT8			*m_lpMemBegin;
	AEUINT8			*m_lpMemEnd;
	CMemChunk		*m_lpCurrent; // the current memory chunk we are operating in.
	CMemChunkList	m_clChunks;  // the collection of all memory chunks used.

// Methods
public:
	CFixedMemory( void )
	{
		m_lpCurrent = 0L;
	}

	void *Allocate( void )
	{
		void *lpRet = m_lpCurrent->Allocate( m_clChunks.m_uiChunkSize, m_clChunks.m_uiMaxChunks );
		if( lpRet == 0 )
			lpRet = m_clChunks.Allocate( m_lpCurrent );

		return lpRet;
	}

	AEUINT8 *Init( AEUINT8 *lpChunkBase, AEUINT32 uiChunkSize, AEUINT32 uiFixedSize )
	{
		m_lpMemBegin = lpChunkBase;
		m_lpMemEnd = lpChunkBase+uiFixedSize;
		m_clChunks.Init( lpChunkBase, uiFixedSize, uiChunkSize, m_lpCurrent );

		return m_lpMemEnd;
	}

};

class AEMiniAllocatorImpl
{
private:
	AEUINT8			*m_lpBaseMem;
	AEUINT8			*m_lpBaseMemEnd;
	AEUINT8			*m_lpChunkStart;
	AEUINT8			*m_lpChunkEnd;
	CMicroChunk		*m_lpLastMicroChunk;
	CMicroChunk		*m_lpMicroChunks;
	
	CFixedMemory	*m_lpFixedAllocators[257];

	AEUINT32		m_uiMaxMicroChunks;
	AEUINT32		m_uiMicroChunkCount;
	AEUINT32		m_uiChunkSize;

	CFixedMemory	m_fmAlloc[6];

public:
	AEMiniAllocatorImpl( int nSize, AEMemoryAllocator *lpHeap = NULL )
	{
		m_lpLastMicroChunk	= 0L;
		m_lpMicroChunks		= 0L;
		m_uiMicroChunkCount	= 0;
		m_uiMaxMicroChunks	= 0;
		m_uiChunkSize		= nSize / 6;

		for( AEUINT32 i=0; i<=8; i++ )
			m_lpFixedAllocators[i] = &m_fmAlloc[0];

		// 9 through 16 bytes
		for( AEUINT32 i=9; i<=16; i++ )
			m_lpFixedAllocators[i] = &m_fmAlloc[1];

		// 17 through 32 bytes
		for( AEUINT32 i=17; i<=32; i++ )
			m_lpFixedAllocators[i] = &m_fmAlloc[2];

		// 33 through 64
		for( AEUINT32 i=33; i<=64; i++ )
			m_lpFixedAllocators[i] = &m_fmAlloc[3];

		// 65 through 128
		for( AEUINT32 i=65; i<=128; i++ )
			m_lpFixedAllocators[i] = &m_fmAlloc[4];

		// 129 through 255
		for( AEUINT32 i=129; i<257; i++ )
			m_lpFixedAllocators[i] = &m_fmAlloc[5];

		int nInitialSize = nSize+32;
		if( lpHeap == NULL )
			m_lpBaseMem = (AEUINT8 *)malloc( nInitialSize );
		else
			m_lpBaseMem = (AEUINT8 *)lpHeap->BlockAlloc( nInitialSize );

		m_lpBaseMemEnd = m_lpBaseMem+nInitialSize;

		AEUINT8 *lpChunkBase = (AEUINT8 *)m_lpBaseMem;

		lpChunkBase+=32;
		AEUINT64 uiPtr = (AEUINT64)lpChunkBase;
		uiPtr = uiPtr>>4;
		uiPtr = uiPtr<<4; // make sure it is 16 byte aligned.
		lpChunkBase = (AEUINT8 *)uiPtr;

		m_lpChunkStart = lpChunkBase;
		lpChunkBase = m_fmAlloc[0].Init( lpChunkBase, 8, m_uiChunkSize );
		lpChunkBase = m_fmAlloc[1].Init( lpChunkBase, 16, m_uiChunkSize );
		lpChunkBase = m_fmAlloc[2].Init( lpChunkBase, 32, m_uiChunkSize );
		lpChunkBase = m_fmAlloc[3].Init( lpChunkBase, 64, m_uiChunkSize );
		lpChunkBase = m_fmAlloc[4].Init( lpChunkBase, 128, m_uiChunkSize );
		lpChunkBase = m_fmAlloc[5].Init( lpChunkBase, 256, m_uiChunkSize );
		m_lpChunkEnd = lpChunkBase;

		AEASSERT( lpChunkBase <= m_lpBaseMemEnd );
	}

	AE_FORCEINLINE void *BlockAlloc( AEUINT32 uiNumBytes, AEMemoryAllocator::AEMemoryAlign maAlign )
	{
		void *lpRet = m_lpFixedAllocators[uiNumBytes]->Allocate();
		return lpRet;
	}

	AE_FORCEINLINE void BlockFree( void *lpPtr, AEUINT32 uiNumBytes )
	{

	}
};

AEMiniAllocator::AEMiniAllocator( int nSize )
{
	int nRealSize = nSize - (nSize % 6);
	m_lpImpl = new AEMiniAllocatorImpl( nRealSize );
}

AEMiniAllocator::AEMiniAllocator( int nSize, AEMemoryAllocator *lpParentAlloc )
{
	int nRealSize = nSize - (nSize % 6);
	m_lpImpl = new AEMiniAllocatorImpl( nRealSize, lpParentAlloc );
}

bool AEMiniAllocator::Init( int nSize, AEMemoryAllocator *lpHeap )
{
	if( m_lpImpl )
		return false;

	int nRealSize = nSize - (nSize % 6);
	m_lpImpl = new AEMiniAllocatorImpl( nRealSize, lpHeap );
	return true;
}

void *AEMiniAllocator::BlockAlloc( AEUINT32 uiNumBytes, AEMemoryAlign maAlign )
{
	return m_lpImpl->BlockAlloc( uiNumBytes, maAlign );
}

void AEMiniAllocator::BlockFree( void *lpPtr, AEUINT32 uiNumBytes )
{
	m_lpImpl->BlockFree( lpPtr, uiNumBytes );
}

AEINT32 AEMiniAllocator::GetAllocatedSize( const void *lpObj, AEINT32 nBytes )
{
	return 1;
}
